# 可以自己import我们平台支持的第三方python模块，比如pandas、numpy等。
# 为了不报错引入的模块
import logging as logger
from util.util import *
from util.finance import *
import util.scheduler as scheduler
# 机器学习使用
from sklearn.preprocessing import StandardScaler
from scipy.stats.mstats import winsorize
from sklearn.linear_model import LinearRegression, Ridge

# numpy ,panda
import pandas as pd
import numpy as np 



# 4、中位数绝对偏差去极值
def median(factor):
    """3倍中位数去极值
    """
    # 求出因子值的中位数
    med = np.median(factor)
    # 求出因子值与中位数的差值，进行绝对值
    mad = np.median(abs(factor - med))

    # 定义几倍的中位数上下限
    high = med + (3 * 1.4826) * mad
    low = med - (3 * 1.4826) * mad

    # 替换上下限以外的值
    factor = np.where(factor > high, high, factor)
    factor = np.where(factor < low, low, factor)
    return factor


def stand(factor):
    """
    自实现标准化
    """
    mean = factor.mean()
    std = factor.std()
    return (factor - mean) / std


# 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。
def init(context):
    context.hs300 = index_components("000300.XSHG")
    context.stocknum = 20
    context.weights = np.array([0.00202422, -0.09036465, -0.03628955, -0.02442507, 0.1839142,
                                0.0857804, 0.02850286, 0.16494613, -0.1418339])

    # 调仓函数
    scheduler.run_monthly(regression_stock, tradingday=1)


def regression_stock(context, bar_dict):
    """回归法进行选择股票
    准备因子数据、数据处理(缺失值、去极值、标准化、中性化)
    预测每个股票对应这一天的结果，然后排序选出前20只股票
    """
    q = query(fundamentals.eod_derivative_indicator.pe_ratio,
              fundamentals.eod_derivative_indicator.pb_ratio,
              fundamentals.eod_derivative_indicator.market_cap,
              fundamentals.financial_indicator.ev,
              fundamentals.financial_indicator.return_on_asset_net_profit,
              fundamentals.financial_indicator.du_return_on_equity,
              fundamentals.financial_indicator.earnings_per_share,
              fundamentals.income_statement.revenue,
              fundamentals.income_statement.total_expense).filter(fundamentals.stockcode.in_(context.hs300))

    factor = get_fundamentals(q).T

    # 2、定义函数去处理数据
    logger.info(factor.head())

    # 去掉了null
    context.factors_data = factor.dropna()

    # 2、因子（特征值）数据进行处理
    dealwith_data(context)

    # 3、根据每月预测下月的收益率大小替换股票池
    select_stocklist(context)

    # 4买卖股票
    balance(context, bar_dict)
    pass


def dealwith_data(context):
    market_cap_factor = context.factors_data['market_cap']

    # 去极值,标准化,还要中性化处理
    factor = context.factors_data
    for factor_name in factor.columns:
        # 去极值 ,标准化
        factor[factor_name] = median(factor[factor_name])
        factor[factor_name] = stand(factor[factor_name])
        # 中性化处理
        if factor_name == 'market_cap':
            continue
        x = market_cap_factor
        y = factor[factor_name]
        lr = LinearRegression()
        # x:要求是二维,y要求是一维
        lr.fit(x.values.reshape(-1, 1), y)
        y_predict = lr.predict(x.values.reshape(-1, 1))

        # 得出的误差替换原来的因子值
        context.factors_data[factor_name] = y - y_predict
        pass
    pass


def select_stocklist(context):
    """
       回归计算预测得出收益率结果，筛选收益率高的股票
       """

    # 特征值是：context.factors_data （300， 9）
    # 系数：因子权重
    # 进行矩阵运算，预测收益率
    # （m行，n列）* （n行，l列） = （m行， l列）
    # (300, 9) * (9, 1) = (300, 1)
    # logger.info(context.factors_data.shape)

    # 预测收益率，如果收益高，那么接下来的下一个月都持有收益高的
    # 这写股票
    stock_return = np.dot(context.factors_data.values, context.weights)
    # 赋值给因子数据,注意都是默认对应的股票代码和收益率
    context.factors_data['stock_return'] = stock_return
    # 进行收益率的排序
    # 修改：按照从大到小的收益率排序，选择前
    context.stock_list = context.factors_data.sort_values(by='stock_return', ascending=False).index[:context.stocknum]
    pass


def balance(context, bar_dict):
    # 调仓频率：一个月，买卖判断
    # 进行每日调仓（多因子选股调仓周期频率会小一些）
    holding_list = context.portfolio.positions.keys()
    will_buy_list = context.stock_list.tolist()
    sell_list = set(holding_list) - set(will_buy_list)
    buy_list = set(will_buy_list) - set(holding_list)

    # 在这里才能进行卖出
    for stock in sell_list:
        if context.portfolio.positions[stock].quantity == 0:
            # 如果旧的持有的股票不在新的股票池当中，卖出
            order_target_percent(stock, 0)

    # 等比例资金买入，投资组合总价值的百分比平分10份
    weight = 1.0 / len(context.stock_list)

    # 买入最新的每日更新的股票池当中的股票
    for stock in buy_list:
        order_target_percent(stock, weight)
    # logger.info(context.portfolio.positions.keys())
    pass


# before_trading此函数会在每天策略交易开始前被调用，当天只会被调用一次
def before_trading(context):
    pass


# 你选择的证券的数据更新将会触发此段逻辑，例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
    pass


# after_trading函数会在每天交易结束后被调用，当天只会被调用一次
def after_trading(context):
    pass
